
#include <cassert>
#include <cstdlib>
#include <vector>

#include <DXGI.h>
#include <D3D10_1.h>
#include <D3DX11.h>
#include <D3DX10math.h>

#include <assimp/Importer.hpp>
#include <assimp/scene.h>
#include <assimp/postprocess.h>

#include "Core\IndexBuffer.h"
#include "Core\Renderer.h"
#include "Core\RenderTarget.h"
#include "Core\Shader.h"
#include "Core\VertexBuffer.h"
#include "Math\Vector4.h"
#include "Tools\Size.h"

#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "d3dx11.lib")
#pragma comment(lib, "d3dx10.lib")


Renderer::Renderer()
	: m_device(NULL),
	  m_context(NULL),
	  m_swap_chain(NULL),
	  m_render_target_view(NULL),
	  m_depth_stencil_view(NULL),
	  m_blend_state_add(NULL),
	  m_blend_state_replace(NULL),
	  m_depth_stencil_state(NULL),
	  m_rasterizer_state(NULL),
	  m_index_buffer(NULL),
	  m_quad_index_buffer(NULL),
	  m_initialized(FALSE)
{
}


Renderer::~Renderer()
{
}


BOOL Renderer::IsValid() CONST
{
	return m_initialized;
}


BOOL Renderer::Initialize(HWND hWnd, CONST Size& resolution, DXGI_FORMAT format, ULONG mode)
{
	// Set initialization flag.
	m_initialized = TRUE;

	BOOL result = FALSE;

	// DX11.
	result = InitializeDX11(hWnd, resolution, format, mode);
	assert(result);

	// Setup the quad index buffer.
	UINT quad_index_buffer[] = {0, 1, 2};	// Comprend pas pourquoi il faut moins de 6 indices.
	UINT count = sizeof(quad_index_buffer) / sizeof(UINT);

	for(UINT8 i = 0; i < count; ++i)
		quad_index_buffer[i] = i;

	m_quad_index_buffer = new IndexBuffer(&quad_index_buffer, count);
	assert(m_quad_index_buffer);

	result = Bind(*m_quad_index_buffer);
	assert(result);

	return TRUE;
}


VOID Renderer::Terminate()
{
	// Terminate the quad index buffer.
	if(m_quad_index_buffer)
	{
		if(m_quad_index_buffer->IsBinded())
		{
			Unbind(*m_quad_index_buffer);
		}

		delete m_quad_index_buffer;
		m_quad_index_buffer = NULL;
	}

	// DX11.
	TerminateDX11();
	
	// Set initialization flag.
	m_initialized = FALSE;
}


VOID Renderer::SetAdditiveBlending(BOOL value)
{
	assert(IsValid());

	if(value)
		m_context->OMSetBlendState(m_blend_state_add, NULL, 0xffffffff);
	else
		m_context->OMSetBlendState(m_blend_state_replace, NULL, 0xffffffff);
}


BOOL Renderer::Bind(VertexBuffer& buffer) CONST
{
	assert(IsValid());
	assert(!buffer.IsBinded());

	buffer.SetDevice(m_device);
	buffer.SetContext(m_context);

	return buffer.Bind();
}


VOID Renderer::Unbind(VertexBuffer& buffer) CONST
{
	assert(IsValid());
	assert(buffer.IsBinded());

	buffer.Unbind();

	buffer.SetDevice(NULL);
	buffer.SetContext(NULL);
}


BOOL Renderer::Bind(IndexBuffer& buffer) CONST
{
	assert(IsValid());
	assert(!buffer.IsBinded());

	buffer.SetDevice(m_device);
	buffer.SetContext(m_context);

	return buffer.Bind();
}


VOID Renderer::Unbind(IndexBuffer& buffer) CONST
{
	assert(IsValid());
	assert(buffer.IsBinded());
		
	buffer.Unbind();	
	
	buffer.SetDevice(NULL);
	buffer.SetContext(NULL);
}


VOID Renderer::SetVertexBuffer(VertexBuffer* buffer)
{
	if(!buffer)
		return;

	assert(IsValid());
	assert(buffer->IsBinded());

	buffer->Enable();
}


VOID Renderer::SetIndexBuffer(IndexBuffer* buffer)
{
	if(!buffer)
		return;

	assert(IsValid());
	assert(buffer->IsBinded());

	buffer->Enable();

	m_index_buffer = buffer;
}


VOID Renderer::SetQuadBuffers()
{
	assert(IsValid());

	SetVertexBuffer(NULL);
	SetIndexBuffer(m_quad_index_buffer);
}


BOOL Renderer::Bind(Shader& shader) CONST
{
	assert(IsValid());
	assert(!shader.IsBinded());

	shader.SetDevice(m_device);
	shader.SetContext(m_context);

	return shader.Bind();
}


VOID Renderer::Unbind(Shader& shader) CONST
{
	assert(IsValid());
	assert(shader.IsBinded());

	shader.Unbind();

	shader.SetDevice(NULL);
	shader.SetContext(NULL);
}


VOID Renderer::SetShader(Shader* shader)
{
	if(!shader)
		return;

	assert(IsValid());
	assert(shader->IsBinded());

	shader->Enable();
}


BOOL Renderer::Bind(RenderTarget& render_target)
{
	assert(IsValid());
	assert(!render_target.IsBinded());

	render_target.SetDevice(m_device);
	render_target.SetContext(m_context);

	return render_target.Bind();
}


VOID Renderer::Unbind(RenderTarget& render_target)
{
	assert(IsValid());
	assert(render_target.IsBinded());

	render_target.Unbind();

	render_target.SetDevice(NULL);
	render_target.SetContext(NULL);
}


VOID Renderer::SetBackbuffer(BOOL enable)
{
	if(enable)
	{
		m_context->OMSetRenderTargets(1, &m_render_target_view, m_depth_stencil_view);
	}
	else
	{
		m_context->OMSetRenderTargets(0, NULL, m_depth_stencil_view);
	}
}


VOID Renderer::SetDepthbuffer(BOOL enable)
{
	if(enable)
	{
		m_context->OMSetDepthStencilState(m_depth_stencil_state, 1);
	}
	else
	{
		m_context->OMSetDepthStencilState(m_depth_stencil_state_disable, 1);
	}
}


VOID Renderer::SetRenderTargets(UINT count, RenderTarget* CONST * render_targets)
{
	// Set standard render target.
	if(!count || !render_targets)
	{
		m_context->OMSetRenderTargets(1, &m_render_target_view, m_depth_stencil_view);
	}
	// Set render targets.
	else
	{
		assert(IsValid());

		ID3D11RenderTargetView* views[D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT];

		for(UINT i = 0; i < count; ++i)
		{
			assert(render_targets[i]->IsBinded());
			views[i] = render_targets[i]->GetRenderTarget();
		}

		m_context->OMSetRenderTargets(count, views, m_depth_stencil_view);
	}
}


VOID Renderer::ClearBackBuffer(CONST Vector4& color)
{
	assert(IsValid());

	FLOAT rgba[] = {color.GetX(), color.GetY(), color.GetZ(), color.GetW()};

	m_context->ClearRenderTargetView(m_render_target_view, rgba);
}


VOID Renderer::ClearBackBuffer(FLOAT r, FLOAT g, FLOAT b, FLOAT a)
{
	assert(IsValid());

	FLOAT rgba[] = {r, g, b, a};

	m_context->ClearRenderTargetView(m_render_target_view, rgba);
}


VOID Renderer::ClearDepthBuffer()
{	
	assert(IsValid());

	m_context->ClearDepthStencilView(m_depth_stencil_view, D3D11_CLEAR_DEPTH, 1.0f, 0);
}


VOID Renderer::ClearStencilBuffer()
{
	assert(IsValid());

	m_context->ClearDepthStencilView(m_depth_stencil_view, D3D11_CLEAR_STENCIL, 0.0f, 1);
}


VOID Renderer::Render()
{
	assert(IsValid());

	if(m_index_buffer)
		m_context->DrawIndexed(m_index_buffer->Size(), 0, 0);
}


VOID Renderer::Swap()
{
	assert(IsValid());

	HRESULT result = m_swap_chain->Present(0, 0);
	
	assert(SUCCEEDED(result));
}


BOOL Renderer::InitializeDX11(HWND hWnd, CONST Size& resolution, DXGI_FORMAT format, ULONG mode)
{
	HRESULT result;
	
	// Create DXGI factory.
	IDXGIFactory* factory = NULL;

	result = CreateDXGIFactory(__uuidof(IDXGIFactory), reinterpret_cast< LPVOID* >(&factory));
	assert(SUCCEEDED(result));

	// Populate adapter vector.
	UINT adapter_index = 0; 
	IDXGIAdapter* adapter = NULL;
	std::vector< IDXGIAdapter* > adapters;

	while((result = factory->EnumAdapters(adapter_index, &adapter)) != DXGI_ERROR_NOT_FOUND) 
	{
		adapters.push_back(adapter);
		++adapter_index;
	}

	factory->Release();
	factory = NULL;

	// Initialize swap chain description.
	DXGI_SWAP_CHAIN_DESC swap_chain_description;

	ZeroMemory(&swap_chain_description, sizeof(swap_chain_description));

	swap_chain_description.BufferCount = 1;

	swap_chain_description.BufferDesc.Width = resolution.GetWidth();
	swap_chain_description.BufferDesc.Height = resolution.GetHeight();
	swap_chain_description.BufferDesc.Format = format;
	swap_chain_description.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
	swap_chain_description.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;

	swap_chain_description.BufferDesc.RefreshRate.Numerator = 0;
	swap_chain_description.BufferDesc.RefreshRate.Denominator = 1;

	swap_chain_description.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
	
	swap_chain_description.OutputWindow = hWnd;
	
	swap_chain_description.SampleDesc.Count = 1;
	swap_chain_description.SampleDesc.Quality = 0;
	
	swap_chain_description.Windowed = TRUE;

	swap_chain_description.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;

	swap_chain_description.Flags = 0;

	// Create device & swap chain.
	D3D_FEATURE_LEVEL feature_level = D3D_FEATURE_LEVEL_11_0;

	adapter_index = 0;

	while(adapter_index == 0 || (FAILED(result) && adapter_index < adapters.size()))
	{
		result = D3D11CreateDeviceAndSwapChain(adapters[adapter_index], D3D_DRIVER_TYPE_UNKNOWN, NULL, 0, &feature_level, 1,
											   D3D11_SDK_VERSION, &swap_chain_description, &m_swap_chain, &m_device,
											   NULL, &m_context);

		++adapter_index;
	}

	assert(SUCCEEDED(result));

	for(adapter_index = 0; adapter_index < adapters.size(); ++adapter_index)
	{
		adapters[adapter_index]->Release();
		adapters[adapter_index] = NULL;
	}

	ID3D11Texture2D* back_buffer = NULL;
	
	result = m_swap_chain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast< LPVOID* >(&back_buffer));
	assert(SUCCEEDED(result));

	result = m_device->CreateRenderTargetView(back_buffer, NULL, &m_render_target_view);

	assert(SUCCEEDED(result));

	back_buffer->Release();
	back_buffer = NULL;

	// Initialize depth-stencil buffer description.
	D3D11_TEXTURE2D_DESC depth_buffer_description;

	ZeroMemory(&depth_buffer_description, sizeof(depth_buffer_description));

	depth_buffer_description.Width = resolution.GetWidth();
	depth_buffer_description.Height = resolution.GetHeight();
	depth_buffer_description.MipLevels = 1;
	depth_buffer_description.ArraySize = 1;
	depth_buffer_description.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
	depth_buffer_description.SampleDesc.Count = 1;
	depth_buffer_description.SampleDesc.Quality = 0;
	depth_buffer_description.Usage = D3D11_USAGE_DEFAULT;
	depth_buffer_description.BindFlags = D3D11_BIND_DEPTH_STENCIL;
	depth_buffer_description.CPUAccessFlags = 0;
	depth_buffer_description.MiscFlags = 0;

	// Create depth-stencil buffer.
	ID3D11Texture2D* depth_stencil_buffer = NULL;

	result = m_device->CreateTexture2D(&depth_buffer_description, NULL, &depth_stencil_buffer);
	assert(SUCCEEDED(result));

	// Initialize depth-stencil state description.
	D3D11_DEPTH_STENCIL_DESC depth_stencil_description;

	ZeroMemory(&depth_stencil_description, sizeof(depth_stencil_description));

	depth_stencil_description.DepthEnable = TRUE;
	depth_stencil_description.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
	depth_stencil_description.DepthFunc = D3D11_COMPARISON_LESS_EQUAL;

	depth_stencil_description.StencilEnable = TRUE;
	depth_stencil_description.StencilReadMask = 0xFF;
	depth_stencil_description.StencilWriteMask = 0xFF;

	depth_stencil_description.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
	depth_stencil_description.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_INCR;
	depth_stencil_description.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
	depth_stencil_description.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS;

	depth_stencil_description.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
	depth_stencil_description.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_DECR;
	depth_stencil_description.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
	depth_stencil_description.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS;

	// Create depth-stencil state enable and enable.
	result = m_device->CreateDepthStencilState(&depth_stencil_description, &m_depth_stencil_state);
	assert(SUCCEEDED(result));

	m_context->OMSetDepthStencilState(m_depth_stencil_state, 1);

	// Create depth-tencil state disable.
	depth_stencil_description.DepthEnable = FALSE;
	
	result = m_device->CreateDepthStencilState(&depth_stencil_description, &m_depth_stencil_state_disable);
	assert(SUCCEEDED(result));

	// Initialize depth-stencil view description.
	D3D11_DEPTH_STENCIL_VIEW_DESC depth_stencil_view_description;
	
	ZeroMemory(&depth_stencil_view_description, sizeof(depth_stencil_view_description));

	depth_stencil_view_description.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
	depth_stencil_view_description.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
	depth_stencil_view_description.Texture2D.MipSlice = 0;

	// Create depth-stencil view.
	result = m_device->CreateDepthStencilView(depth_stencil_buffer, &depth_stencil_view_description, &m_depth_stencil_view);
	assert(SUCCEEDED(result));

	// Bind to the context.
	m_context->OMSetRenderTargets(1, &m_render_target_view, m_depth_stencil_view);

	// Initialize rasterizer description.
	D3D11_RASTERIZER_DESC rasterizer_description;

	ZeroMemory(&rasterizer_description, sizeof(rasterizer_description));

	rasterizer_description.AntialiasedLineEnable = FALSE;
	rasterizer_description.CullMode = D3D11_CULL_BACK;
	rasterizer_description.DepthBias = 0;
	rasterizer_description.DepthBiasClamp = 0.0f;
	rasterizer_description.DepthClipEnable = FALSE;
	rasterizer_description.FillMode = D3D11_FILL_SOLID;
	rasterizer_description.FrontCounterClockwise = FALSE;
	rasterizer_description.MultisampleEnable = FALSE;
	rasterizer_description.ScissorEnable = FALSE;
	rasterizer_description.SlopeScaledDepthBias = 0.0f;

	// Create rasterizer state and enable.
	result = m_device->CreateRasterizerState(&rasterizer_description, &m_rasterizer_state);
	assert(SUCCEEDED(result));

	m_context->RSSetState(m_rasterizer_state);

	// Initialize blend state description.
	D3D11_BLEND_DESC blend_description;
	ZeroMemory(&blend_description, sizeof(blend_description));

	blend_description.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE;
	blend_description.RenderTarget[0].DestBlend = D3D11_BLEND_ONE;
	blend_description.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
	blend_description.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
	blend_description.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ONE;
	blend_description.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
	blend_description.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;

	// Initialize and create replace blend state.
	blend_description.RenderTarget[0].BlendEnable = FALSE;

	result = m_device->CreateBlendState(&blend_description, &m_blend_state_replace);
	assert(SUCCEEDED(result));

	// Initialize and create additive blend state.
	blend_description.RenderTarget[0].BlendEnable = TRUE;

	result = m_device->CreateBlendState(&blend_description, &m_blend_state_add);
	assert(SUCCEEDED(result));

	// Set to replace blend state.
	m_context->OMSetBlendState(m_blend_state_replace, NULL, 0xffffffff);

	// Initialize viewport.
	D3D11_VIEWPORT viewport;
	
	viewport.Width = static_cast< FLOAT >(resolution.GetWidth());
	viewport.Height = static_cast< FLOAT >(resolution.GetHeight());
	viewport.MinDepth = 0.0f;
	viewport.MaxDepth = 1.0f;
	viewport.TopLeftX = 0.0f;
	viewport.TopLeftY = 0.0f;

	// Create the viewport.
	m_context->RSSetViewports(1, &viewport);
	
	// Set primitive to draw, support only for triangles.
	m_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

	return TRUE;
}


VOID Renderer::TerminateDX11()
{
	if(m_render_target_view)
	{
		m_render_target_view->Release();
		m_render_target_view = NULL;
	}

	if(m_depth_stencil_view)
	{
		m_depth_stencil_view->Release();
		m_depth_stencil_view = NULL;
	}

	if(m_blend_state_add)
	{
		m_blend_state_add->Release();
		m_blend_state_add = NULL;
	}

	if(m_blend_state_replace)
	{
		m_blend_state_replace->Release();
		m_blend_state_replace = NULL;
	}

	if(m_rasterizer_state)
	{
		m_rasterizer_state->Release();
		m_rasterizer_state = NULL;
	}

	if(m_depth_stencil_state)
	{
		m_depth_stencil_state->Release();
		m_depth_stencil_state = NULL;
	}

	if(m_context)
	{
		m_context->Release();
		m_context = NULL;
	}

	if(m_device)
	{
		m_device->Release();
		m_device = NULL;
	}

	if(m_swap_chain)
	{
		m_swap_chain->Release();
		m_swap_chain = NULL;
	}
}